﻿using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.IO;
using System.Linq;

namespace ErrorDumper.PropertyExtracting {
    public static class ExceptionPropertyExtractors {

        private static readonly object _syncRoot = new object();

        private static readonly HashSet<IExceptionPropertyExtractor>
            _globalExtractors = new HashSet<IExceptionPropertyExtractor>();

        private static readonly Dictionary<Type, HashSet<IExceptionPropertyExtractor>>
            _typeExtractors = new Dictionary<Type, HashSet<IExceptionPropertyExtractor>>();

        static ExceptionPropertyExtractors() {
            RegisterTypeExtractor<FileNotFoundException, FileNotFoundExceptionPropertyExtractor>();
            RegisterTypeExtractor<SqlException, SqlExceptionPropertyExtractor>();
        }

        public static bool RegisterGlobalExtractor(IExceptionPropertyExtractor extractor) {
            if (extractor == null)
                throw new ArgumentNullException("extractor");
            lock (_syncRoot)
                return _globalExtractors.Add(extractor);
        }

        public static bool RegisterGlobalExtractor<TExtractor>()
            where TExtractor : class, IExceptionPropertyExtractor, new() {
            lock (_syncRoot)
                return !_globalExtractors.Any(i => i.GetType() == typeof(TExtractor))
                    ? _globalExtractors.Add(new TExtractor()) : false;
        }

        public static bool UnregisterGlobalExtractor(IExceptionPropertyExtractor extractor) {
            if (extractor == null)
                throw new ArgumentNullException("extractor");
            lock (_syncRoot)
                return _globalExtractors.Remove(extractor);
        }

        public static int UnregisterGlobalExtractor(Type extractorType) {
            if (extractorType == null)
                throw new ArgumentNullException("extractorType");
            if (!typeof(IExceptionPropertyExtractor).IsAssignableFrom(extractorType))
                throw new ArgumentOutOfRangeException("extractorType");
            lock (_syncRoot)
                return _globalExtractors.RemoveWhere(i => i.GetType() == extractorType);
        }

        public static int UnregisterGlobalFilter<TExtractor>()
            where TExtractor : class, IExceptionPropertyExtractor {
            lock (_syncRoot)
                return _globalExtractors.RemoveWhere(i => i.GetType() == typeof(TExtractor));
        }

        public static int UnregisterGlobalExtractor(Predicate<IExceptionPropertyExtractor> predicate) {
            if (predicate == null)
                throw new ArgumentNullException("predicate");
            lock (_syncRoot)
                return _globalExtractors.RemoveWhere(predicate);
        }

        public static bool RegisterTypeExtractor(Type type, IExceptionPropertyExtractor extractor) {
            if (type == null)
                throw new ArgumentNullException("type");
            if (extractor == null)
                throw new ArgumentNullException("extractor");

            lock (_syncRoot) {
                HashSet<IExceptionPropertyExtractor> filters;
                if (!_typeExtractors.TryGetValue(type, out filters))
                    _typeExtractors.Add(type, filters = new HashSet<IExceptionPropertyExtractor>());
                return filters.Add(extractor);
            }
        }

        public static bool RegisterTypeExtractor<T>(IExceptionPropertyExtractor extractor) {
            return RegisterTypeExtractor(typeof(T), extractor);
        }

        public static bool RegisterTypeExtractor<T, TExtractor>()
            where TExtractor : class, IExceptionPropertyExtractor, new() {

            lock (_syncRoot) {
                HashSet<IExceptionPropertyExtractor> extractors;
                if (!_typeExtractors.TryGetValue(typeof(T), out extractors))
                    _typeExtractors.Add(typeof(T), extractors = new HashSet<IExceptionPropertyExtractor>());

                return !extractors.Any(i => i.GetType() == typeof(TExtractor))
                    ? extractors.Add(new TExtractor()) : false;
            }
        }

        public static ICollection<IExceptionPropertyExtractor> GetGlobalExtractors() {
            lock (_syncRoot)
                return _globalExtractors.ToArray();
        }

        public static ICollection<IExceptionPropertyExtractor> GetTypeExtractors(Func<Type, bool> typePredicate) {
            if (typePredicate == null)
                throw new ArgumentNullException("typePredicate");
            lock (_syncRoot)
                return (from entry in _typeExtractors
                        where typePredicate(entry.Key)
                        from extractor in entry.Value
                        select extractor).ToArray();
        }

        public static void ExtractProperties(object instance, ExceptionPropertyCollection properties) {
            if (instance == null)
                throw new ArgumentNullException("instance");
            if (properties == null)
                throw new ArgumentNullException("properties");

            foreach (var extractor in GetGlobalExtractors())
                extractor.ExtractProperties(instance, properties);

            foreach (var extractor in GetTypeExtractors(t => t.IsInstanceOfType(instance)))
                extractor.ExtractProperties(instance, properties);
        }
    }
}